/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package fr.inria.openmath.omapi.implementation;

/**
 *
 * @author Mr.Sam
 */
import java.io.*;

/**
 * Decodes a BASE64 encoded input stream to some output stream.
 * This class implements BASE64 decoding, as specified in the
 * <a href="http://ds.internic.net/rfc/rfc1521.txt">MIME specification</a>.
 * This code is strongly inspired from that of the W3C jigsaw.
 * @see fr.inria.openmath.omapi.implementation.Base64Encoder
 */

public class Base64Decoder {

    private static final int BUFFER_SIZE = 1024;

    InputStream in = null;

    OutputStream out = null;



    private final int get1(byte buf[], int off) {
        return ((buf[off] & 0x3f) << 2) | ((buf[off + 1] & 0x30) >>> 4);
    }

    private final int get2(byte buf[], int off) {
        return ((buf[off + 1] & 0x0f) << 4) | ((buf[off + 2] & 0x3c) >>> 2);
    }

    private final int get3(byte buf[], int off) {
        return ((buf[off + 2] & 0x03) << 6) | (buf[off + 3] & 0x3f);
    }

    private final int check(int ch) {
        if ((ch >= 'A') && (ch <= 'Z')) {
            return ch - 'A';
        } else if ((ch >= 'a') && (ch <= 'z')) {
            return ch - 'a' + 26;
        } else if ((ch >= '0') && (ch <= '9')) {
            return ch - '0' + 52;
        } else {
            switch (ch) {
                case '=': 
                    return 65;
                case '+': 
                    return 62;
                case '/': 
                    return 63;
                default: 
                    return -1;
            }
        }
    }

    /**
     * Does the actual decoding.
     *
     * <p>Process the input stream by decoding it and emiting the resulting
     * bytes into the output stream.
     * @exception IOException If the input or output stream accesses failed.
     * @exception Base64FormatException If the input stream is not compliant
     *    with the BASE64 specification.
     */

    private void process() throws IOException, Base64FormatException {
        byte buffer[] = new byte[BUFFER_SIZE];
        byte chunk[] = new byte[4];
        int got = -1;
        int ready = 0;

        fill: while ((got = in.read(buffer)) > 0) {
            int skiped = 0;
            while (skiped < got) {
                // Check for un-understood characters:
                while (ready < 4) {
                    if (skiped >= got)
                        continue fill;
                    int ch = check(buffer[skiped++]);
                    if (ch >= 0)
                        chunk[ready++] = (byte) ch;
                }
                if (chunk[2] == 65) {
                    out.write(get1(chunk, 0));
                    return;
                } else if (chunk[3] == 65) {
                    out.write(get1(chunk, 0));
                    out.write(get2(chunk, 0));
                    return;
                } else {
                    out.write(get1(chunk, 0));
                    out.write(get2(chunk, 0));
                    out.write(get3(chunk, 0));
                }
                ready = 0;
            }
        }
        if (ready != 0)
            throw new Base64FormatException("Invalid length.");
        out.flush();
    }

    /**
     * Decodes a String.
     *
     * <p>Does the decoding, and returns a String.
     * @exception Base64FormatException If the input string is not compliant
     *     with the BASE64 specification.
     */

    public String processString(String input) throws Base64FormatException {
        this.in = new ByteArrayInputStream(input.getBytes());
        this.out = new ByteArrayOutputStream();

        try {
            process();
        } catch (IOException e) {
            throw new Base64FormatException("Unterminated input.");
        }
        return ((ByteArrayOutputStream) out).toString();
    }

    /**
     * Tests the decoder.
     *
     * <p>Runs it with one argument: the string to be decoded, it will print out
     * the decoded value.
     */

    public static void main(String args[]) {
        if (args.length == 1) {
            try {
                Base64Decoder b = new Base64Decoder();
                System.out.println("[" + b.processString(args[0]) + "]");
            } catch (Base64FormatException e) {
                System.out.println("Invalid Base64 format !");
                System.exit(1);
            }
        } else {
            System.out.println("Base64Decoder [string]");
        }
        System.exit(0);
    }

}
